package org.eclipse.epsilon.emc.rdbms.iModel;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.models.EolEnumerationValueNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelElementTypeNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.eclipse.epsilon.eol.exceptions.models.EolNotInstantiableModelElementTypeException;
import org.eclipse.epsilon.eol.execute.introspection.AbstractPropertyGetter;
import org.eclipse.epsilon.eol.execute.introspection.AbstractPropertySetter;
import org.eclipse.epsilon.eol.execute.introspection.IPropertyGetter;
import org.eclipse.epsilon.eol.execute.introspection.IPropertySetter;
import org.eclipse.epsilon.eol.models.Model;
import org.eclipse.epsilon.emc.dummydriver.DummyModelElement;
import org.eclipse.epsilon.emc.rdbms.dbConnection.*;

public class DBReflexiveModel extends Model {	
	protected static final boolean debug = false;  //This is so we can implement error messaging for debugging. This will be removed from final version.
	protected static ArrayList<String> arrTables = new ArrayList<String>();
	protected ArrayList<DBReflexiveModelElement> contents = new ArrayList<DBReflexiveModelElement>();
	H2Connection con = new H2Connection("jdbc:h2:","~","epsilon"); //Create a new connection object to the H2 database  
	
	@Override
	/**
	 * This loads all the tables from the database into an array and validates 
	 * validates that the objects can be instantiated. 
	 * <p>
	 * @return      the image at the specified URL
	 * @see         isInstantiable
	 * @see         getSQLTables
	 */
	public void load() throws EolModelLoadingException
	{	
		ArrayList<?> arrTablesTemp = new ArrayList();
		System.out.println("Loading database tables names into the model");
			arrTablesTemp = con.getSQLTables();  //get a list of all the database tables.
		System.out.println("Validating that all Objects are instantiable");
			for (int i=0; i < arrTablesTemp.size(); i++) 
			{	
				//We need to check if these tables can be instantiated (need to look at many to many relationships).
				if(isInstantiable(arrTablesTemp.get(i).toString())==true) 
				{
				arrTables.add(arrTablesTemp.get(i).toString());
				}	
			}
    }
		
	@Override
	//NA dont need to worry about this one.
	public Object getEnumerationValue(String enumeration, String label)
	    throws EolEnumerationValueNotFoundException {
		System.out.println("getEnumerationValue");
		return null;
	}

	@Override
	//Return a model element for each row in the database, this shouldnt really be needed for this implementation
	public Collection<?> allContents() 
	{
		System.out.println("allContents");
		return contents;
	}


	/**
	 * This gets and created all of the Objects which are currently in the database. 
	 * This is the same as performing a select * from tablebame
	 * <p>
	 * @param  type This is the name of the table in the SQL database.
	 * @return      the result set of all the objects in the database
	 * @see         
	 * @see         
	 */
	@Override
	//bring back select * from ObjectName bring back a list of rows from a table.
	public Collection<?> getAllOfType(String type) throws EolModelElementTypeNotFoundException 
	{
		System.out.println("getAllOfType");
		ArrayList<DBReflexiveModelElement> result = new ArrayList<DBReflexiveModelElement>();		
		//This should bring back all elements in a model so should bring back a resultset containing all rows in the table.
		ArrayList<String[]> rows = null;
		try {rows= con.getAllRows(type);} 
		catch (SQLException e) {e.printStackTrace();}
	    //Now that we have an arrayList for the table we can create the Primary Key Columns based this.
		ArrayList<String> primaryKeyIds = con.getPrimaryKey(type);
       //Now we have the results back we need to create a new model element and add the results to the model element.
		DBReflexiveModelElement element = new DBReflexiveModelElement(type);
		String PKString = null;	
		for(String[] value : rows) 
			{ 	
			  //We now need to get the primary key identifier so we can identify each individual row.
				for (int k=0; k < primaryKeyIds.size() ; k++) 
				{				
			    	PKString = value[Integer.parseInt(primaryKeyIds.get(k))].toString();
			    	if (k < primaryKeyIds.size()-1)
			    	{
			    	PKString.concat("_");	
			    	}
				}
				//Add the data to the element.
				for (int i=0; i < rows.get(0).length; i++) 
				{		
					element.put(PKString, value[i]);
			    }
				result.add(element);
			}	
			return result;
   }

	@Override
	public Collection<?> getAllOfKind(String type) throws EolModelElementTypeNotFoundException 
	{
		System.out.println("getAllOfKind");  //Should be exactly the same of get allofType....
	    return getAllOfType(type);		
	}
	
	

	@Override
	//This should return DBReflexiveModelElement.class.
	public Object getTypeOf(Object instance) 
	{
		
		
		System.out.println("getTypeOf");
		return instance.getClass();
	}

	@Override
	//DBRelflexiveModel should return the name of the table which the row comes from.
	public String getTypeNameOf(Object instance) 
	{
		
		System.out.println("getTypeNameOf");
		return ((DBReflexiveModelElement) instance).getType();
	}

	@Override
	//Create a new row of the table specified.
	public Object createInstance(String type)
			throws EolModelElementTypeNotFoundException,
			EolNotInstantiableModelElementTypeException {
		System.out.println("createInstance");
		//Here we need to look at doing transactional support as we dont want the database to fail but the Object to be created.
		DBReflexiveModelElement newElement = new DBReflexiveModelElement(type);	
		String key = con.putNewRow(type);
		contents.add(newElement);
		return newElement;
	}
	@Override
	//String id is the tablename_PK of the database table
	public Object getElementById(String id) 
	{
		
		
		//Get the model element based on the Primary Key of the table.
		System.out.println("getElementById");
		return null;
	}

	@Override
	//Checks to see the value for the PK if no PK is implemented then return null.
	public String getElementId(Object instance) 
	{
	//Get the primary Key for the Object.
		System.out.println("getElementId");
		return null;
	}

	@Override
	//This should not be implemented as we must use the PK assignment from the database so you are not allowed to set the PK Value
	public void setElementId(Object instance, String newId) {
		System.out.println("setElementId");
		System.out.println("Warning: You cannot set the Primary Key for the Object");
	}

	@Override
	//Delete Row from Database.
	public void deleteElement(Object instance) throws EolRuntimeException {
		System.out.println("deleteElement");
		/************************************************************************************
		 * Work In progress
		 ***********************************************************************************/
		//We need to check of the object that is passed to the method is the PK for the table if not we cannot delete the data.
		
		//Create a new string builder
		
		//Start the transaction
		//DELETE FROM Object where get the PK list we need to build up this statement dynamically based on what the columns for the PK are if it has a composite PK as the PK in the
		//Object which is passed is a surrogate PK and does not exists withing the database iteself.
		
	    //Either commit or rollback the transaction here.
		//Once this has been deleted then we are OK, if not we need to rollback the transaction
		
		
		
		/************************************************************************************
		 *  End of Work in Progress
		 ***********************************************************************************/
		
		//Remove the contents from the Object.
		contents.remove(instance);
	}

	@Override
	//Owns will check if a model owns an object.  What exactly is owns supposed to do ???? cant remember.
	public boolean owns(Object instance) 
	{
     
		
	
		return contents.contains(instance);
	}

	@Override
	//returns true look at many to many relationships here...
	public boolean isInstantiable(String type)
	{	
	//This will only instantiate Objects which have a Primary Key defined in the table.
		if(con.getPrimaryKey(type).size() > 0)
		{	
		return true;
		}
		else 
		{
		System.out.println("The Table does not have a primary Key defined and therefore cannot be instantiated");	
		return false;
		}
	}

	@Override
	//Checks to see of it is a DBReflexiveModelElement owns superceeds this..
	public boolean isModelElement(Object instance) {
		System.out.println("isModelElement");
		return true;
	}

	@Override
	//If there is a table of that name in the model if there is then it returns true of it does not it returns false.
	public boolean hasType(String type) 
	{
	System.out.println("Checking that the table exists in the database table list");	
	
	if(arrTables.contains(type)) 
	{return true;}
	else
	{return false;}
	}   

	@Override
	//This does not need to do anything as we are not creating tables in the datababase although that could be one for the future?
	public boolean store(String location) {
		System.out.println("location");
		return false;
	}

	@Override
	//This does not need to do anything as we are not creating tables in the datababase although that could be one for the future?
	public boolean store() 
	{
		System.out.println("store");
		// TODO Auto-generated method stub
		return false;
	}
	
	@Override
	//returns implementation of iPropertyGetter.
	public IPropertyGetter getPropertyGetter() 
	{
		System.out.println("getPropertyGetter");
		return new ElementPropertyGetter();
	}
	
	@Override
	//Setting the 
	public IPropertySetter getPropertySetter() {
		System.out.println("getPropertySetter");
		return new ElementPropertySetter();
	}
	
	class ElementPropertyGetter extends AbstractPropertyGetter {
		@Override
		//Gets Model Element and Column Name.
		public Object invoke(Object object, String property)
				throws EolRuntimeException {
			System.out.println("invoke ElementPropertyGetter");
			return ((DBReflexiveModelElement) object).getProps().get(property);
		}
		
	}
	
	class ElementPropertySetter extends AbstractPropertySetter {
       //This is for an update to the database.
		@Override
		public void invoke(Object value) throws EolRuntimeException {
			System.out.println("invoke ElementPropertySetter");
			//We need to find the PK for the relevant table and then update the database with the result from the Object.			
			//Does this replace the entire Object in the database i.e update all columns or is it supposed to update one column
			//Looks like it does the entire Object
			//This will replace the Object value pair.

			try {
				con.generateUpdateStatement(property, value);
			} 
			catch (SQLException e) 
			{
				e.printStackTrace();
			}

			
			((DBReflexiveModelElement) object).getProps().put(property, value);
		}	
	}
}




